/****************************************************************************
 * Copyright (c) 2008, 2009 Andreas Unger and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Andreas Unger - initial API and implementation 
 ****************************************************************************/

package org.eclipselabs.damos.simulation.internal.launching;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipselabs.damos.dml.Component;
import org.eclipselabs.damos.dml.DMLFactory;
import org.eclipselabs.damos.dml.FloatingPointDataType;
import org.eclipselabs.damos.dml.FloatingPointFormat;
import org.eclipselabs.damos.dml.Fragment;
import org.eclipselabs.damos.dml.Inoutport;
import org.eclipselabs.damos.dml.InputPort;
import org.eclipselabs.damos.dml.OutputPort;
import org.eclipselabs.damos.execution.datatype.DataTypeResolver;
import org.eclipselabs.damos.execution.datatype.InvalidDataTypeException;
import org.eclipselabs.damos.execution.executionmodel.ExecutionModel;
import org.eclipselabs.damos.execution.executionmodel.Node;
import org.eclipselabs.damos.execution.executionmodel.builder.DeadlockException;
import org.eclipselabs.damos.execution.executionmodel.builder.ExecutionModelBuilder;
import org.eclipselabs.damos.simulation.AbstractComponentSimulationObject;
import org.eclipselabs.damos.simulation.ComponentSimulationObject;
import org.eclipselabs.damos.simulation.SimulationContext;
import org.eclipselabs.damos.simulation.SimulationException;
import org.eclipselabs.damos.simulation.Value;
import org.eclipselabs.damos.simulation.ValueFactory;
import org.eclipselabs.damos.simulation.internal.SimulationContextImpl;
import org.eclipselabs.damos.simulation.internal.services.ComponentSimulationService;

/**
 * @author Andreas Unger
 *
 */
public class SimulationProcess implements IProcess {

	private ILaunch launch;
	private String name;

	private Job job;
	private boolean terminated;

	/**
	 * 
	 */
	public SimulationProcess(ILaunch launch, String name) {
		this.launch = launch;
		this.name = name;
		launch.addProcess(this);
		fireCreationEvent();
	}

	public ILaunch getLaunch() {
		return launch;
	}
	
	public String getLabel() {
		return name;
	}

	public void simulate(SimulationContextImpl context, Fragment fragment) throws MissingComponentSimulationObjectException, DeadlockException, InvalidDataTypeException {
		ExecutionModel executionModel = new ExecutionModelBuilder().build(fragment);
		
		FloatingPointDataType defaultDataType = DMLFactory.eINSTANCE.createFloatingPointDataType();
		defaultDataType.setFormat(FloatingPointFormat.BINARY64);

		new DataTypeResolver().resolve(executionModel, defaultDataType);

		adaptSimulationObjects(context, executionModel);
		
		job = new SimulationJob(executionModel, context);
		job.addJobChangeListener(new JobChangeAdapter() {
			public void done(IJobChangeEvent event) {
				terminated = true;
				fireTerminateEvent();
			}
		});
		job.schedule();
	}
	
	protected void adaptSimulationObjects(SimulationContext context, ExecutionModel executionModel) throws MissingComponentSimulationObjectException {
		List<Component> missingSimulationObjectBlocks = new ArrayList<Component>();
		
		for (Node node : executionModel.getNodes()) {
			Component component = node.getComponent();
			ComponentSimulationObject componentSimulationObject;
			if (component instanceof Inoutport) {
				componentSimulationObject = new InoutportSimulationObject();
			} else {
				componentSimulationObject = ComponentSimulationService.getInstance().createSimulationObject(component);
			}
			if (componentSimulationObject != null) {
				componentSimulationObject.setNode(node);
				componentSimulationObject.setContext(context);
				node.setData(componentSimulationObject);
			} else {
				missingSimulationObjectBlocks.add(component);
			}
		}
		
		if (!missingSimulationObjectBlocks.isEmpty()) {
			throw new MissingComponentSimulationObjectException(missingSimulationObjectBlocks);
		}
	}

	public boolean canTerminate() {
		return !terminated;
	}

	public boolean isTerminated() {
		return terminated;
	}

	public void terminate() throws DebugException {
		terminated = job.cancel();
	}

	public String getAttribute(String key) {
		return null;
	}

	public void setAttribute(String key, String value) {
	}

	public IStreamsProxy getStreamsProxy() {
		return null;
	}

	public int getExitValue() throws DebugException {
		return job.getResult().getCode();
	}

	/**
	 * Fires a creation event.
	 */
	protected void fireCreationEvent() {
		fireEvent(new DebugEvent(this, DebugEvent.CREATE));
	}

	/**
	 * Fires a terminate event.
	 */
	protected void fireTerminateEvent() {
		fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
	}

	/**
	 * Fires the given debug event.
	 * 
	 * @param event debug event to fire
	 */
	protected void fireEvent(DebugEvent event) {
		DebugPlugin manager= DebugPlugin.getDefault();
		if (manager != null) {
			manager.fireDebugEventSet(new DebugEvent[] { event });
		}
	}

	@SuppressWarnings("unchecked")
	public Object getAdapter(Class adapter) {
		return null;
	}
	
	private static class InoutportSimulationObject extends AbstractComponentSimulationObject {
		
		private Value inputValue;
		
		public void initialize() throws SimulationException {
			inputValue = ValueFactory.INSTANCE.newValue(this, getComponentDataType(), 0);
		}
		
		public void consumeInputValue(InputPort inputPort, Value value) throws SimulationException {
			inputValue = value;
		}
		
		public Value getOutputValue(OutputPort outputPort) throws SimulationException {
			return inputValue;
		}
		
	}

}
